// Package failover
/**
* @Project : geektime-basic-go-study
* @File    : timeout.go
* @IDE     : GoLand
* @Author  : Tvux
* @Date    : 2024/10/23 11:49
**/

package failover

import (
	"context"
	"errors"
	"geektime-basic-go-study/webook/backend/internal/service/sms"
	"log"
	"sync/atomic"
)

// SMSTimeoutFailoverService 带有超时故障转移的短信服务
type SMSTimeoutFailoverService struct {
	svcs      []sms.Service // 短信服务提供商列表
	idx       int32         // 当前使用的服务提供商索引
	cnt       int32         // 连续超时次数
	threshold int32         // 超时故障转移阈值
}

func (Self *SMSTimeoutFailoverService) Send(ctx context.Context, tplId string, args []string, phones ...string) error {
	// 原子地加载当前索引和连续超时次数
	idx := atomic.LoadInt32(&Self.idx)
	cnt := atomic.LoadInt32(&Self.cnt)

	// 如果连续超时次数超过阈值，尝试切换到下一个服务提供商
	if cnt >= Self.threshold {
		// 计算新的索引，循环服务提供商列表
		newIdx := (idx + 1) % int32(len(Self.svcs))
		// 原子地更新索引，确保只有一个线程能成功更新
		if atomic.CompareAndSwapInt32(&Self.idx, idx, newIdx) {
			// 成功更新索引后，重置连续超时次数
			atomic.StoreInt32(&Self.cnt, 0)
		}
		// 如果更新失败，说明其他线程已经更新了索引
		idx = atomic.LoadInt32(&Self.idx)
	}

	// 选择当前索引对应的服务提供商
	svc := Self.svcs[idx]
	err := svc.Send(ctx, tplId, args, phones...)
	if err == nil {
		log.Printf("SMS sent successfully using provider: %s", svc.GetProvider())
		// 发送成功，重置连续超时次数
		atomic.StoreInt32(&Self.cnt, 0)
		return nil
	}
	// err != nil
	log.Printf("Failed to send SMS using provider: %s, error: %v", svc.GetProvider(), err)
	if errors.Is(err, context.DeadlineExceeded) {
		// 发送超时，增加连续超时次数
		atomic.AddInt32(&Self.cnt, 1)
	}

	// 对于非超时错误，不切换服务提供商，直接返回错误
	return err
}

// GetProvider 方法返回当前服务提供商的名称。
func (Self *SMSTimeoutFailoverService) GetProvider() sms.ProviderName {
	idx := atomic.LoadInt32(&Self.idx)
	return Self.svcs[idx].GetProvider()
}

func NewSMSTimeoutFailoverService(threshold int32, smsSvcs ...sms.Service) *SMSTimeoutFailoverService {
	return &SMSTimeoutFailoverService{
		threshold: threshold,
		svcs:      smsSvcs,
	}
}
